#include <tty.h>
#include <device.h>
#include <errno.h>

static int tty_open(struct device *dev)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);
	int ret;

	dev_flags_clr_can_seek(dev->flags);
	dev_oflags_clr_seek(dev->oflags);

	ret = tty_ldisc_open(tty);
	if (!ret) {
		if (tdrv->ops->open)
			ret = tdrv->ops->open(tty);
	}
	return ret;
}

static void tty_close(struct device *dev)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);

	tty_ldisc_close(tty);
	if (tdrv->ops->close)
		tdrv->ops->close(tty);
}

static int tty_read(struct device *dev, void *buf, unsigned int len)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);
	struct tty_ldisc *ld;
	int ret = 0;

	ld = tty_ldisc_get(tty);
	if (ld && !IS_ERR(ld) && tty_flags_is_ldisc_enabled(tty->flags) && ld->ops.read)
		ret = ld->ops.read(tty, buf, len);
	else if (tdrv->ops->read)
		ret = tdrv->ops->read(tty, buf, len);
	else 
		ret = -EIO;

	return ret;
}

static int tty_write(struct device *dev, const void *buf, unsigned int len)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);
	struct tty_ldisc *ld;
	int ret = 0;

	ld = tty_ldisc_get(tty);
	if (ld && !IS_ERR(ld) && tty_flags_is_ldisc_enabled(tty->flags) && ld->ops.write)
		ret = ld->ops.write(tty, buf, len);
	else if (tdrv->ops->write)
		ret = tdrv->ops->write(tty, buf, len);
	else
		ret = -EIO;

	return ret;
}

static loff_t no_llseek(struct device *dev, loff_t offset, unsigned int origin)
{
	return -ENOTSUPP;
}

static int tty_ioctl(struct device *dev, unsigned int cmd, unsigned int arg)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);
	struct tty_ldisc *ld;
	int ret = 0;

	ld = tty_ldisc_get(tty);
	if (ld && !IS_ERR(ld) && tty_flags_is_ldisc_enabled(tty->flags) && ld->ops.ioctl)
		ret = ld->ops.ioctl(tty, cmd, arg);
		
	if ((ret == -ENOTSUPP) && (tdrv->ops->ioctl))
		ret = tdrv->ops->ioctl(tty, cmd, arg);

	return ret;
}

static void tty_flush(struct device *dev)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = device_to_tty_driver(dev);
	struct tty_ldisc *ld;

	ld = tty_ldisc_get(tty);	
	if (ld && !IS_ERR(ld) && tty_flags_is_ldisc_enabled(tty->flags) && ld->ops.flush)
		ld->ops.flush(tty);
	if (tdrv->ops->flush)
		tdrv->ops->flush(tty);
}

static int tty_match(struct device *dev, struct driver *drv)
{
	struct tty_driver *tdrv = driver_to_tty_driver(drv);

	if (tdrv->ops->match)
		return tdrv->ops->match(dev, tdrv);
	
	return -ENOTSUPP;
}

static int tty_probe(struct device *dev, struct driver *drv)
{
	struct tty *tty = device_to_tty(dev);
	struct tty_driver *tdrv = driver_to_tty_driver(drv);

	if (tdrv->ops->probe)
		return tdrv->ops->probe(tty, tdrv);
	
	return 0;
}

struct device_operations tty_ops = {
	.open	= tty_open,
	.close	= tty_close,
	.read	= tty_read,
	.write	= tty_write,
	.seek	= no_llseek,
	.ioctl	= tty_ioctl,
	.flush	= tty_flush,
	.match	= tty_match,
	.probe	= tty_probe,
};

